// SDL2_11 [OpenGL ES Cube 5].nova // Interactive OpenGL ES cube using a loaded '.jpg' texture. // Using namespace declarations. using library.emscripten; using library.math; using library.opengl; using library.sdl2; class OpenGL_Quad { public float b; public float t; public float l; public float r; } // The application class. class SDL2_11_OpenGL_ES_Cube_5 { // Static data members. private static SDL_Window w; private static SDL_Renderer r; private static SDL_GLContext c; private static uint sizeX; private static uint sizeY; private static int Pmatrix; private static int Vmatrix; private static int Mmatrix; private static uint[] textures; private static int textureLoc; private static uint index_buffer; private static float[] proj_matrix; private static float[] mov_matrix; private static float[] view_matrix; private static int mouseX, mouseY; private static double spinX, spinY; private static bool mouseButtonDown; private static bool done; // Application class's "main" function. public static void main( String[] args ) { Stream.writeLine( "SDL2_11 [OpenGL ES Cube 5].nova" ); // Initialise the class's data members. mouseX = 0; mouseY = 0; spinX = 0; spinY = 0; mouseButtonDown = false; done = false; sizeX = 640; sizeY = 480; // Disable the emscripten full-screen controls. Emscripten.disableControls( ); // Setup SDL. SDL2.SDL_Init( SDL2.SDL_INIT_VIDEO ); SDL2.SDL_GL_SetAttribute( SDL2.SDL_GL_CONTEXT_MAJOR_VERSION, 2 ); SDL2.SDL_GL_SetAttribute( SDL2.SDL_GL_CONTEXT_MINOR_VERSION, 0 ); SDL2.SDL_GL_SetAttribute( SDL2.SDL_GL_DOUBLEBUFFER, 1 ); SDL2.SDL_GL_SetAttribute( SDL2.SDL_GL_DEPTH_SIZE, 24 ); w = SDL2.SDL_CreateWindow( "SDL2_11_OpenGL_ES_Cube_5", SDL2.SDL_WINDOWPOS_CENTERED, SDL2.SDL_WINDOWPOS_CENTERED, (int)sizeX, (int)sizeY, SDL2.SDL_WINDOW_OPENGL | SDL2.SDL_WINDOW_RESIZABLE ); // Check for a null reference. if ( w == null ) { // Output an error message. Stream.writeLine( "Failed to create window: " + SDL2.SDL_GetError( ) ); // Abort the application. return; } r = SDL2.SDL_CreateRenderer( w, -1, SDL2.SDL_RENDERER_ACCELERATED | SDL2.SDL_RENDERER_PRESENTVSYNC ); c = SDL2.SDL_GL_CreateContext( w ); // Set the background colour. OpenGL.glClearColor( 0.0f, 0.0f, 0.0f, 1.0f ); float[] vertices = { -1,-1,-1, 1,-1,-1, 1, 1,-1, -1, 1,-1, -1,-1, 1, 1,-1, 1, 1, 1, 1, -1, 1, 1, -1,-1,-1, -1, 1,-1, -1, 1, 1, -1,-1, 1, 1,-1,-1, 1, 1,-1, 1, 1, 1, 1,-1, 1, -1,-1,-1, -1,-1, 1, 1,-1, 1, 1,-1,-1, -1, 1,-1, -1, 1, 1, 1, 1, 1, 1, 1,-1 }; float[] textureCoordinates = { // Front 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, // Back 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, // Top 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, // Bottom 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, // Right 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, // Left 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0 }; sbyte[] indices = { 0,1,2, 0,2,3, 4,5,6, 4,6,7, 8,9,10, 8,10,11, 12,13,14, 12,14,15, 16,17,18, 16,18,19, 20,21,22, 20,22,23 }; uint[] vbos = new uint[ 2 ]; OpenGL.glGenBuffers( 2, vbos ); uint verticesMemSize = vertices.getMemorySize( ); uint texCoordsMemSize = textureCoordinates.getMemorySize( ); // Create and store data into vertex buffer. uint vertex_buffer = vbos[ 0 ]; OpenGL.glBindBuffer( OpenGL.GL_ARRAY_BUFFER, vertex_buffer ); OpenGL.glBufferData( OpenGL.GL_ARRAY_BUFFER, verticesMemSize + texCoordsMemSize, OpenGL.GL_DYNAMIC_DRAW ); OpenGL.glBufferSubData( OpenGL.GL_ARRAY_BUFFER, 0, vertices ); OpenGL.glBufferSubData( OpenGL.GL_ARRAY_BUFFER, verticesMemSize, textureCoordinates ); // Create and store data into index buffer. index_buffer = vbos[ 1 ]; OpenGL.glBindBuffer( OpenGL.GL_ELEMENT_ARRAY_BUFFER, index_buffer ); OpenGL.glBufferData( OpenGL.GL_ELEMENT_ARRAY_BUFFER, indices, OpenGL.GL_DYNAMIC_DRAW ); // The shaders. String vertCode = "attribute vec3 aPosition; \n" + "attribute vec2 aTextureCoord; \n" + "uniform mat4 Pmatrix; \n" + "uniform mat4 Vmatrix; \n" + "uniform mat4 Mmatrix; \n" + "varying vec2 vTextureCoord; \n" + " \n" + "void main( void ) \n" + "{ \n" + " gl_Position = Pmatrix * Vmatrix * Mmatrix * vec4( aPosition, 1.0 ); \n" + " vTextureCoord = aTextureCoord; \n" + "} \n"; String fragCode = "precision mediump float; \n" + "varying vec2 vTextureCoord; \n" + "uniform sampler2D tex; \n" + " \n" + "void main( void ) \n" + "{ \n" + " gl_FragColor = texture2D( tex, vTextureCoord ); \n" + "} \n"; uint vertShader = OpenGL.glCreateShader( OpenGL.GL_VERTEX_SHADER ); OpenGL.glShaderSource( vertShader, vertCode ); OpenGL.glCompileShader( vertShader ); uint fragShader = OpenGL.glCreateShader( OpenGL.GL_FRAGMENT_SHADER ); OpenGL.glShaderSource( fragShader, fragCode ); OpenGL.glCompileShader( fragShader ); uint shaderProgram = OpenGL.glCreateProgram( ); OpenGL.glAttachShader( shaderProgram, vertShader ); OpenGL.glAttachShader( shaderProgram, fragShader ); OpenGL.glLinkProgram( shaderProgram ); OpenGL.glUseProgram( shaderProgram ); // Get the uniform locations. Pmatrix = OpenGL.glGetUniformLocation( shaderProgram, "Pmatrix" ); Vmatrix = OpenGL.glGetUniformLocation( shaderProgram, "Vmatrix" ); Mmatrix = OpenGL.glGetUniformLocation( shaderProgram, "Mmatrix" ); // Position. OpenGL.glBindBuffer( OpenGL.GL_ARRAY_BUFFER, vertex_buffer ); int position = OpenGL.glGetAttribLocation( shaderProgram, "aPosition" ); OpenGL.glVertexAttribPointer( (uint)position, 3, OpenGL.GL_FLOAT, false, 0, 0 ); OpenGL.glEnableVertexAttribArray( (uint)position ); int texCoordAttLoc = OpenGL.glGetAttribLocation( shaderProgram, "aTextureCoord" ); OpenGL.glEnableVertexAttribArray( (uint)texCoordAttLoc ); OpenGL.glVertexAttribPointer( (uint)texCoordAttLoc, 2, OpenGL.GL_FLOAT, false, 0, verticesMemSize ); textures = loadTexture( ); if ( textures == null ) { Stream.writeLine( "Failed to load texture." ); return; } textureLoc = OpenGL.glGetUniformLocation( shaderProgram, "tex" ); // Calculate the projection matrix. // float angle = 30.0f, aspectRatio = sizeX / sizeY, zMin = 1.0f, zMax = 100.0f; // float ang = Math.tan( ( angle * 0.5f ) * (float)Math.PI / 180.0f ); /* proj_matrix = { 0.5f / ang, 0 , 0, 0, 0, 0.5f * aspectRatio / ang, 0, 0, 0, 0, -( zMax + zMin ) / ( zMax - zMin ), -1, 0, 0, ( -2.0f * zMax * zMin ) / ( zMax - zMin ), 0 };*/ float fovyInDegrees = 45.0f; float aspectRatio = (float)sizeX / (float)sizeY; float near = 1.0f; float far = 100.0f; proj_matrix = { 1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1 }; // setProjectionMatrix( 30.0f, 1.0f, 100.0f, proj_matrix ); OpenGL_Quad q = gluPerspective( fovyInDegrees, aspectRatio, near, far ); glFrustum( q, near, far, proj_matrix ); mov_matrix = { 1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1 }; view_matrix = { 1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1 }; // Translating z. view_matrix[ 14 ] -= 5; // zoom. // Orientate the cube. rotateY( mov_matrix, 45.0f ); rotateX( mov_matrix, 30.0f ); // Check for the emscripten environment. if ( Emscripten.isActive( ) ) { int simulate_infinite_loop = 1; // Call the function repeatedly. int fps = -1; // Call the function as fast as the browser wants to render (typically 60fps). Emscripten.setMainLoop( renderFrame, fps, simulate_infinite_loop ); } else { // Rendering and event processing loop. do { renderFrame( ); } while( !done ); } // Shutdown app. OpenGL.glDeleteTextures( 1, textures ); OpenGL.glDeleteProgram( shaderProgram ); OpenGL.glDeleteShader( vertShader ); OpenGL.glDeleteShader( fragShader ); OpenGL.glDeleteBuffers( 2, vbos ); SDL2.SDL_GL_DeleteContext( c ); SDL2.SDL_DestroyRenderer( r ); SDL2.SDL_DestroyWindow( w ); SDL2.SDL_Quit( ); } public static void setProjectionMatrix( float angleOfView, float near, float far, float[] m ) { // Set the basic projection matrix. float scale = 1.0f / Math.tan( angleOfView * 0.5f * (float)Math.PI / 180.0f ); m[ 0 ] = scale; // Scale the x coordinates of the projected point. m[ 5 ] = scale; // Scale the y coordinates of the projected point. m[ 10 ] = -far / (far - near); // Used to remap z to [ 0, 1 ]. m[ 11 ] = -1.0f; // Set w = -z. m[ 14 ] = -far * near / (far - near); // Used to remap z [ 0, 1 ]. m[ 15 ] = 0.0f; } // Compute screen coordinates. public static OpenGL_Quad gluPerspective( float fovY, float aspect, float zNear, float zFar ) { float scale = Math.tan( fovY * 0.5f * (float)Math.PI / 180.0f ) * zNear; OpenGL_Quad q = new OpenGL_Quad( ); q.r = aspect * scale; q.l = -q.r; q.t = scale; q.b = -q.t; return q; } // Set the OpenGL perspective projection matrix. public static void glFrustum( OpenGL_Quad q, float n, float f, float[] m ) { // Set OpenGL perspective projection matrix. m[ 0 ] = 2 * n / ( q.r - q.l ); m[ 1 ] = 0; m[ 2 ] = 0; m[ 3 ] = 0; m[ 4 ] = 0; m[ 5 ] = 2 * n / ( q.t - q.b ); m[ 6 ] = 0; m[ 7 ] = 0; m[ 8 ] = ( q.r + q.l ) / ( q.r - q.l ); m[ 9 ] = ( q.t + q.b ) / ( q.t - q.b ); m[ 10 ] = -( f + n ) / ( f - n ); m[ 11 ] = -1; m[ 12 ] = 0; m[ 13 ] = 0; m[ 14 ] = -2 * f * n / ( f - n ); m[ 15 ] = 0; } public static void onMouseButtonDown( SDL_Event e ) { // Stream.writeLine( "SDL_MOUSEBUTTONDOWN" ); SDL_MouseButtonEvent mbEvent = (SDL_MouseButtonEvent)e; // Check for a non null reference after the object cast. if ( mbEvent != null ) { // byte b = mbEvent.button; // Stream.writeLine( Byte.toString( b ) ); if ( mbEvent.button == SDL2.SDL_BUTTON_LEFT ) { // Stream.writeLine( "Left button pressed." ); // Update the state of the left mouse button. mouseButtonDown = true; // Stop the cube from spinning. spinX = spinY = 0; } } } public static void onMouseButtonUp( SDL_Event e ) { // Stream.writeLine( "SDL_MOUSEBUTTONUP" ); SDL_MouseButtonEvent mbEvent = (SDL_MouseButtonEvent)e; // Check for a non null reference after the object cast. if ( mbEvent != null ) { // Byte b = new Byte( mbEvent.button ); // Stream.writeLine( b.toString( ) ); if ( mbEvent.button == SDL2.SDL_BUTTON_LEFT ) { // Stream.writeLine( "Left button released." ); // Update the state of the left mouse button. mouseButtonDown = false; } } } public static void onMouseMotion( SDL_Event e ) { // Stream.writeLine( "SDL_MOUSEMOTION" ); SDL_MouseMotionEvent mmEvent = (SDL_MouseMotionEvent)e; // Check for a non null reference after the object cast. if ( mmEvent != null ) { int x = mmEvent.x; int y = mmEvent.y; // Stream.writeLine( "x = " + Integer.toString( x ) + " y = " + Integer.toString( y ) ); if ( ( mmEvent.state & SDL2.SDL_BUTTON_LMASK ) != 0 ) { // Stream.writeLine( "Left button pressed (mouse motion)." ); // SDL_Log( "Mouse Button 1 (left) is pressed." ); spinX = (double)( y - mouseY ) / 2.0; spinY = (double)( x - mouseX ) / 2.0; // Orientate the cube. rotateCube( mov_matrix ); mouseButtonDown = true; } mouseX = x; mouseY = y; } } public static void renderFrame( ) { SDL_Event e = SDL2.SDL_PollEvent( ); if ( e != null ) { switch ( e.id ) { case SDL2.SDL_MOUSEBUTTONDOWN : { onMouseButtonDown( e ); break; } case SDL2.SDL_MOUSEBUTTONUP : { onMouseButtonUp( e ); break; } case SDL2.SDL_MOUSEMOTION : { onMouseMotion( e ); break; } case SDL2.SDL_QUIT : { // Stream.writeLine( "SDL_QUIT" ); done = true; break; } } } // Check if the mouse button is up. if ( mouseButtonDown == false ) { // Spin the cube. rotateCube( mov_matrix ); } OpenGL.glEnable( OpenGL.GL_DEPTH_TEST ); OpenGL.glDepthFunc( OpenGL.GL_LESS ); OpenGL.glClearColor( 0.0f, 0.0f, 0.0f, 1.0f ); OpenGL.glClearDepth( 1.0 ); OpenGL.glViewport( 0, 0, (int)sizeX, (int)sizeY ); OpenGL.glClear( OpenGL.GL_COLOR_BUFFER_BIT | OpenGL.GL_DEPTH_BUFFER_BIT ); OpenGL.glUniformMatrix4fv( Pmatrix, 1, false, proj_matrix ); OpenGL.glUniformMatrix4fv( Vmatrix, 1, false, view_matrix ); OpenGL.glUniformMatrix4fv( Mmatrix, 1, false, mov_matrix ); // Tell OpenGL we want to affect texture unit 0. OpenGL.glActiveTexture( OpenGL.GL_TEXTURE0 ); // Bind the texture to texture unit 0. OpenGL.glBindTexture( OpenGL.GL_TEXTURE_2D, textures[ 0 ] ); // Point the uniform sampler to texture unit 0. OpenGL.glUniform1i( textureLoc, 0 ); OpenGL.glBindBuffer( OpenGL.GL_ELEMENT_ARRAY_BUFFER, index_buffer ); OpenGL.glDrawElements( OpenGL.GL_TRIANGLES, 36, OpenGL.GL_UNSIGNED_BYTE ); SDL2.SDL_GL_SwapWindow( w ); } // Rotate the cube. public static void rotateCube( float[] mov_matrix ) { // Rotate the rotation matrix. rotateY( mov_matrix, (float)spinY ); rotateX( mov_matrix, (float)spinX ); } public static void rotateZ( float[] m, float angle ) { angle *= (float)( Math.PI / 180.0 ); // Convert to radians. float c = Math.cos( angle ); float s = Math.sin( angle ); float mv0 = m[ 0 ], mv4 = m[ 4 ], mv8 = m[ 8 ]; m[ 0 ] = c * m[ 0 ] - s * m[ 1 ]; m[ 4 ] = c * m[ 4 ] - s * m[ 5 ]; m[ 8 ] = c * m[ 8 ] - s * m[ 9 ]; m[ 1 ] = c * m[ 1 ] + s * mv0; m[ 5 ] = c * m[ 5 ] + s * mv4; m[ 9 ] = c * m[ 9 ] + s * mv8; } public static void rotateX( float[] m, float angle ) { angle *= (float)( Math.PI / 180.0 ); // Convert to radians. float c = Math.cos( angle ); float s = Math.sin( angle ); float mv1 = m[ 1 ], mv5 = m[ 5 ], mv9 = m[ 9 ]; m[ 1 ] = m[ 1 ] * c - m[ 2 ] * s; m[ 5 ] = m[ 5 ] * c - m[ 6 ] * s; m[ 9 ] = m[ 9 ] * c - m[ 10 ] * s; m[ 2 ] = m[ 2 ] * c + mv1 * s; m[ 6 ] = m[ 6 ] * c + mv5 * s; m[ 10 ] = m[ 10 ] * c + mv9 * s; } public static void rotateY( float[] m, float angle ) { angle *= (float)( Math.PI / 180.0 ); // Convert to radians. float c = Math.cos( angle ); float s = Math.sin( angle ); float mv0 = m[ 0 ], mv4 = m[ 4 ], mv8 = m[ 8 ]; m[ 0 ] = c * m[ 0 ] + s * m[ 2 ]; m[ 4 ] = c * m[ 4 ] + s * m[ 6 ]; m[ 8 ] = c * m[ 8 ] + s * m[ 10 ]; m[ 2 ] = c * m[ 2 ] - s * mv0; m[ 6 ] = c * m[ 6 ] - s * mv4; m[ 10 ] = c * m[ 10 ] - s * mv8; } public static uint[] loadTexture( ) { // Load the texture data from the texture file. JpgImageData imageData = SDL2.loadJpgFile( "joeyswee.jpg", true ); if ( imageData == null ) { return null; } uint[] textures = new uint[ 1 ]; OpenGL.glGenTextures( 1, textures ); OpenGL.glBindTexture( OpenGL.GL_TEXTURE_2D, textures[ 0 ] ); OpenGL.glTexImage2D( OpenGL.GL_TEXTURE_2D, 0, (int)OpenGL.GL_RGB, imageData.width, imageData.height, 0, OpenGL.GL_RGB, OpenGL.GL_UNSIGNED_BYTE, imageData.pixels ); OpenGL.glTexParameteri( OpenGL.GL_TEXTURE_2D, OpenGL.GL_TEXTURE_MIN_FILTER, (int)OpenGL.GL_LINEAR ); OpenGL.glTexParameteri( OpenGL.GL_TEXTURE_2D, OpenGL.GL_TEXTURE_MAG_FILTER, (int)OpenGL.GL_LINEAR ); return textures; } }